package com.zyl.blog.common.exception;

import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.util.SaResult;
import com.zyl.blog.common.enums.ResultCode;
import com.zyl.blog.common.response.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import java.util.List;
import java.util.stream.Collectors;

/**
 * 全局异常处理器（优先级：特定异常 > 通用异常）
 *
 * <p>设计原则：
 * 1. 业务异常优先捕获，避免敏感信息泄露
 * 2. 参数校验异常统一结构化返回，便于前端解析
 * 3. 未知异常兜底处理，生产环境隐藏堆栈详情
 * 4. 日志分级记录（WARN级别不打印堆栈，ERROR级别记录完整堆栈）
 *
 * @version v1.0
 * @ProjectName: Personal_Blog
 * @Package: com.zyl.common.exception
 * @ClassName: GlobalExceptionHandler
 * @Author: FinalFantasy
 * @Date: 2025/2/14-14:11
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /*-------------------- 1. 自定义业务异常处理 --------------------*/
    /**
     * 处理自定义业务异常 {@link BlogException}
     *
     * <p>触发场景：
     * 1. 业务逻辑中主动抛出：throw new EccpException(ResultCode.USER_NOT_FOUND)
     * 2. 服务层校验失败时抛出
     *
     * @param e 自定义业务异常实例，包含预定义的错误码和消息
     * @return 标准化响应，包含 code/message，例如：
     * {
     *   "code": 1001,
     *   "message": "用户不存在",
     *   "data": null
     * }
     * @see BlogException 自定义异常类定义
     * @see ResultCode 错误码枚举
     */
    @ExceptionHandler(BlogException.class)
    public Result<?> handleBlogException(BlogException e) {
        // 注意：业务异常无需打印堆栈（避免日志冗余），WARN级别足够
        log.warn("[业务异常] 错误码: {}, 异常信息: {}", e.getCode(), e.getMessage());
        return Result.of(e.getCode(), e.getMessage(), null);
    }


    /**
     *全局异常拦截（拦截项目中的NotLoginException异常）
     * @param nle NotLoginException
     * @return {@link SaResult }
     */
    @ExceptionHandler(NotLoginException.class)
    public SaResult handlerNotLoginException(NotLoginException nle) {

        /* 打印堆栈，以供调试 */
        SaManager.getLog().error("---------- sa全局异常 ", nle);

        /* 判断场景值，定制化异常信息 */
        String message = switch (nle.getType()) {
            case NotLoginException.NOT_TOKEN -> "未能读取到有效 token";
            case NotLoginException.INVALID_TOKEN -> "token 无效";
            case NotLoginException.TOKEN_TIMEOUT -> "token 已过期";
            case NotLoginException.BE_REPLACED -> "token 已被顶下线";
            case NotLoginException.KICK_OUT -> "token 已被踢下线";
            case NotLoginException.TOKEN_FREEZE -> "token 已被冻结";
            case NotLoginException.NO_PREFIX -> "未按照指定前缀提交 token";
            default -> "当前会话未登录";
        };

        /* 返回给前端 */
        return SaResult.error(message);
    }

    /*-------------------- 2. 参数校验异常处理（精细化分类） --------------------*/
    /**
     * 处理表单参数绑定异常 {@link BindException}
     *
     * <p>触发场景：
     * 使用 @Validated 校验非 @RequestBody 参数时（如GET请求参数校验）
     *
     * @param e 参数绑定异常实例，包含校验失败的字段信息
     * @return 结构化错误列表，例如：
     * {
     *   "code": 400,
     *   "message": "参数校验失败",
     *   "data": [
     *     "age: 必须大于0",
     *     "name: 不能为空"
     *   ]
     * }
     * @see org.springframework.validation.annotation.Validated
     */
    @ExceptionHandler(BindException.class)
    public Result<?> handleBindException(BindException e) {
        List<String> errors = e.getFieldErrors().stream()
                // 格式：字段名: 错误信息（例如："age: 必须大于0"）
                .map(error -> error.getField() + ": " + error.getDefaultMessage())
                .collect(Collectors.toList());

        log.warn("[参数校验异常] 对象类型={}, 字段错误={}", e.getObjectName(), errors);
        return Result.of(ResultCode.PARAM_IS_INVALID.getCode(), "参数校验失败", errors);
    }

    /**
     * 处理请求体参数校验异常 {@link MethodArgumentNotValidException}
     *
     * <p>触发场景：
     * 使用 @Valid 校验 @RequestBody 参数时（如POST JSON参数校验）
     *
     * @param e 参数校验异常实例，包含JSON校验失败详情
     * @return 错误信息列表（格式同BindException），例如：
     * {
     *   "code": 400,
     *   "message": "参数校验失败",
     *   "data": [
     *     "邮箱格式不正确",
     *     "密码长度需6-20位"
     *   ]
     * }
     * @see: javax.validation.Valid
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<?> handleMethodArgumentNotValid(MethodArgumentNotValidException e) {
        List<String> errors = e.getBindingResult().getFieldErrors().stream()
                // 直接提取错误信息（通常已包含字段上下文）
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.toList());

        String paramName = e.getParameter().getParameter().getName();
        log.warn("[请求体校验异常] 参数名={}, 错误详情={}", paramName, errors);
        return Result.of(ResultCode.PARAM_IS_INVALID.getCode(), "参数校验失败", errors);
    }

    /**
     * 处理参数类型不匹配异常 {@link MethodArgumentTypeMismatchException}
     *
     * <p>触发场景：
     * 前端传入参数类型与后端声明类型不匹配（例如：字符串传给整数类型）
     *
     * @param e 类型不匹配异常实例
     * @return 明确类型错误提示，例如：
     * {
     *   "code": 400,
     *   "message": "参数类型错误: 参数名=age, 期望类型=Integer",
     *   "data": null
     * }
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public Result<?> handleTypeMismatch(MethodArgumentTypeMismatchException e) {
        String errorMsg = String.format("参数类型错误: 参数名=%s, 期望类型=%s",
                e.getName(),
                e.getRequiredType() != null ? e.getRequiredType().getSimpleName() : "未知类型");

        log.warn("[参数类型异常] {}", errorMsg);
        return Result.fail(errorMsg).code(ResultCode.PARAM_IS_INVALID.getCode());
    }

    /*-------------------- 3. 未知异常兜底处理 --------------------*/
    /**
     * 处理所有未明确捕获的异常 {@link Exception}
     *
     * <p>设计策略：
     * 1. 开发环境返回完整错误信息，便于调试
     * 2. 生产环境返回友好提示，避免敏感信息泄露
     * 3. 记录完整的错误堆栈，便于后续排查
     *
     * @param e 未被前面处理器捕获的异常实例
     * @return 生产环境示例：
     * {
     *   "code": 500,
     *   "message": "系统繁忙，请稍后重试",
     *   "data": null
     * }
     */
    @ExceptionHandler(Exception.class)
    public Result<?> handleException(Exception e) {
        // 关键！打印堆栈跟踪（否则无法定位问题根源）
        log.error("[系统未知异常] 异常类型={}, 错误信息={}", e.getClass().getName(), e.getMessage(), e);

        // 根据环境返回不同信息（示例）
        String errorMessage = isProductionEnvironment() ? "系统繁忙，请稍后重试" : e.getMessage();
        return Result.fail(errorMessage).code(ResultCode.INTERNAL_ERROR.getCode());
    }

    /**
     * 判断当前是否为生产环境（根据实际项目配置实现）
     */
    private boolean isProductionEnvironment() {
        // 示例：实际项目中可通过读取 Spring Profile 判断
        // return Arrays.stream(env.getActiveProfiles()).anyMatch(p -> p.equalsIgnoreCase("prod"));
        return false;
    }

    /**
     * 处理大模型服务异常
     *
     * @param e 大模型服务异常实例
     * @return {@link Result }<{@link ? }>
     */
    @ExceptionHandler(HttpClientErrorException.class)
    public Result<?> handleOllamaException(HttpClientErrorException e) {
        return Result.fail("大模型服务异常：" + e.getLocalizedMessage());
    }
}